package com.mygame.client.dispatch;

import com.mygame.common.codec.DataCodecKit;
import com.mygame.common.component.BaseRoute;
import com.mygame.common.component.ParamInfo;
import com.mygame.common.component.RouteInfo;
import com.mygame.common.component.annotation.Route;
import com.mygame.common.component.exception.CoreException;
import com.mygame.message.dispatcher.IGameChannelContext;
import com.mygame.common.protobuf.ExternalMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;

import java.lang.reflect.Method;
import java.util.*;

@Service
public class DispatchGameMessageService {
    private Logger logger = LoggerFactory.getLogger(DispatchGameMessageService.class);
    @Autowired
    public ApplicationContext applicationContext;// 注入spring上下文


    private Map<Integer, RouteInfo> routeMaps = new HashMap<>();
    /**
     *
     * <p>
     * Description: 服务启动的时候调用此方法，扫描获取此服务要处理的game message类。
     * </p>
     *
     * @param applicationContext
     * @param serviceId 服务id，如果为0,则加载所有的消息类型，如果不为零，则只加载此类型的消息。
     * @param packagePath 消息所在的包路径
     * @author wgs
     * @date 2019年4月27日 下午7:05:11
     *
     */
    public static void scanGameMessages(ApplicationContext applicationContext, int serviceId, String packagePath) {// 构造一个方便的调用方法
        DispatchGameMessageService dispatchGameMessageService = applicationContext.getBean(DispatchGameMessageService.class);
        dispatchGameMessageService.scanGameMessages(serviceId, packagePath);
    }

    public void scanGameMessages(int serviceId, String packagePath) {
        Collection<BaseRoute> baseRoutes = getBeanListOfType(BaseRoute.class);
        for (BaseRoute route : baseRoutes) {
            Class<?> clazz = getAnnotationClazz(route);
            if (clazz == null) {
                return;
            }
            Route routeAnnotation = clazz.getAnnotation(Route.class);
            Method[] methodArray = clazz.getMethods();
            for (Method method : methodArray) {
                method.setAccessible(true);
                RouteInfo routeInfo = RouteInfo.valueOf(route, clazz, routeAnnotation, method);
                if(routeInfo != null) {
                    this.addRouteInfo(routeInfo);
                }
            }
        }
    }

    public Object[] buildParams(ExternalMessage gameMessage, RouteInfo routeInfo,IGameChannelContext ctx) {
        final var len = routeInfo.paramInfos.length;
        final var params = new Object[len];
        for (int i = 0; i < len; i++) {
            // 方法参数信息
            ParamInfo paramInfo = routeInfo.paramInfos[i];
            // flow 上下文
            if (IGameChannelContext.class.isAssignableFrom(paramInfo.paramClazz)) {
                params[i] = ctx;
                continue;
            }
            params[i] = DataCodecKit.decode(gameMessage.getData(), paramInfo.paramClazz);
        }
        return params;
    }

    public void callMethod(ExternalMessage gameMessage, IGameChannelContext ctx) {// 当收到网络消息之后，调用此方法。
        RouteInfo routeInfo = getRouteInfo(gameMessage.getMessageId());
        if (routeInfo.method.isVoid()) {
            try {
                var params = buildParams(gameMessage, routeInfo, ctx);
                routeInfo.invoke(params);
            } catch (Exception  e) {
                logger.error("调用方法异常，方法所在类：{}，方法名：{}", routeInfo.routeName, e);
            }
        }
    }

    public <T> List<T> getBeanListOfType(Class<T> clazz) {
        List<T> result = new ArrayList<>();
        Map<String, T> map = applicationContext.getBeansOfType(clazz);
        if (null != map) {
            result.addAll(map.values());
        }
        return result;
    }

    private void addRouteInfo(RouteInfo routeInfo) {
        if (routeMaps.containsKey(routeInfo.routeName)) {
            throw new CoreException("routeName duplicate");
        }
        if(routeInfo.handlerAnnotation != null){
            int cmdId = routeInfo.handlerAnnotation.cmd().id;
            routeMaps.put(cmdId, routeInfo);
        }
        if(routeInfo.rpcAnnotation != null){
            int cmdId = routeInfo.rpcAnnotation.cmd().id;
            routeMaps.put(cmdId, routeInfo);
        }
    }
    public RouteInfo getRouteInfo(Integer route) {
        return routeMaps.get(route);
    }

    private Class<?> getAnnotationClazz(BaseRoute instance) {
        Class<?> annotationClazz = null;
        Route annotationRoute = instance.getClass().getAnnotation(Route.class);
        if (annotationRoute != null) {
            annotationClazz = instance.getClass();
        } else {
            Class<?>[] interfacesClazz = instance.getClass().getInterfaces();
            for (Class<?> clazz : interfacesClazz) {
                annotationRoute = clazz.getAnnotation(Route.class);
                if (annotationRoute != null) {
                    annotationClazz = clazz;
                    break;
                }
            }
        }
        return annotationClazz;
    }
}
